/*
 * FileUtils.java
 *
 *  created: 23.8.2011
 *  charset: UTF-8
 *  license: MIT (X11) (See LICENSE file for full license)
 */
package cz.mp.util;

import java.io.*;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;

/**
 * 
 * 
 * @author Martin Pokorný
 * @version 0.1
 */
public class FileUtils {
    
    /** Znaky, které se nesmí objevit v cestě v souborovém systému. */
    public static final String DENIED_PATH_CHARS = "/*?\\\\:\"<|>";
        
    public static final int MAX_FILENAME_LENGTH = 127;
    
    // -----
    
    /** */
    private FileUtils() {        
    }

    /**
     * Kopíruje soubor {@code srcFile} do {@code dstFile}.
     * Pokud {@code dstFile} existuje, je přepsán.
     *
     * @param srcFile  soubor, jež má být zkopírován
     * @param dstFile  nová kopie souboru {@code srcFile}
     * @throws FileNotFoundException  pokud {@code srcFile} neexistuje nebo
     *      není právo ho číst
     * @throws IOException  pokud nelze zkopírovat nebo 
     *      pokud je zdrojový a cílový soubor stejný
     * @throws IllegalArgumentException  pokud je zadán {@code null}
     */
    public static void copyFile (final File srcFile, File dstFile)
            throws FileNotFoundException, IOException {
        if (srcFile == null) {
            throw new IllegalArgumentException("srcFile=null");
        }
        if (dstFile == null) {
            throw new IllegalArgumentException("dstFile=null");
        }
        if (srcFile.equals(dstFile)) {
            throw new IOException("srcFile = dstFile!");
        }
        if (!srcFile.exists() || !srcFile.canRead()) {
            throw new FileNotFoundException(srcFile.getAbsolutePath());
        }

        // kopírování pomocí kanálů
        FileChannel srcChannel = new FileInputStream(srcFile).getChannel();
        FileChannel dstChannel = new FileOutputStream(dstFile).getChannel();

        dstChannel.transferFrom(srcChannel, 0, srcChannel.size());

        srcChannel.close();
        dstChannel.close();
    }

    /**
     * Kopíruje soubor {@code srcFile} do {@code dstFile}.
     * Pokud {@code dstFile} existuje, je přepsán.
     *
     * @param srcFile  jméno souboru, jež má být zkopírován
     * @param dstFile  nové kopie souboru {@code srcFile}
     * @throws IOException  pokud nelze zkopírovat
     * @throws FileNotFoundException  pokud {@code srcFile} nelze číst
     * @throws IllegalArgumentException
     */
    public static void copyFile (final String srcFile, final String dstFile)
            throws IOException {
        copyFile(new File(srcFile), new File(dstFile));
    }
    
    /**
     * Získá příponu souboru.
     * 
     * @param fileName
     * @return přípona souboru (bez znaku {@literal .})
     * @throws IllegalArgumentException
     */
    public static String getFileExtension (final String fileName) {
        return getFileExtension(new File(fileName));
    }
    
    /**
     * Získá příponu souboru.
     * 
     * @param file
     * @return přípona souboru (bez znaku {@literal .})
     * @throws IllegalArgumentException
     */    
    public static String getFileExtension (final File file) {
        if (file == null) {
            throw new IllegalArgumentException("file=null");
        }
        
        String fileName = file.getName().trim();
        if (!fileName.contains(".")) {
            return "";
        }
        if (fileName.lastIndexOf(".") == 0) {
            return "";
        }
        return fileName.substring(fileName.lastIndexOf(".") + 1);        
    }
    
    /**
     * Testuje, zda má zadaný soubor jednu ze zadaných přípon.
     * 
     * @param file
     * @param exts
     * @return 
     * @throws IllegalArgumentException
     */
    public static boolean hasFileExtension(final String file, String... exts) {
        if (StringUtils.isBlank(file)) {
            throw new IllegalArgumentException("file is blank");
        }
        if (exts == null) {
            throw new IllegalArgumentException("exts is null");
        }

        String fileExt = getFileExtension(file);
        for (String ext : exts) {
            if (fileExt.equalsIgnoreCase(ext)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Testuje, zda má zadaný soubor jednu ze zadaných přípon.
     * 
     * @param file
     * @param exts
     * @return 
     * @throws IllegalArgumentException
     */
    public static boolean hasFileExtension(
            final String file, Collection<String> exts) {
        return hasFileExtension(file, (String[]) exts.toArray(new String[0]));
    }
    
    
    /**
     * Získá jméno souboru bez cesty a bez přípony.
     * <p>
     * např:
     * <pre>
     * {@literal a/a.txt --> a}
     * </pre>
     * 
     * @return 
     * @throws IllegalArgumentException
     */
    public static String getFileBaseName(String path) {
        if (path == null) {
            throw new IllegalArgumentException("path = null");
        }    
        
        return FileUtils.getFilePathWithoutExt(new File(path).getName());
    }
    
    
    /**
     * Získá jméno souboru bez přípony, ale s původní cestou.
     * <p>
     * např:
     * <pre>
     * {@literal a/a.txt --> a/a}
     * {@literal a.txt   --> a}
     * </pre>
     * 
     * @param path
     * @return 
     * @throws IllegalArgumentException
     */
    public static String getFilePathWithoutExt(String path) {
        if (path == null) {
            throw new IllegalArgumentException("path = null");
        }
        
        if (!path.contains(".")) {
            return path;
        }
        if (path.indexOf(".") == 0 && 
                path.indexOf(".") == path.lastIndexOf(".")) {
           return path;
        }
        return path.substring(0, path.lastIndexOf("."));        
    }
        
    /**
     * Získá cestu adresáře zadaného souboru.
     * <p>
     * např:
     * <pre>
     * {@literal A/a/a.txt --> A/a}
     * {@literal a/a.txt   --> a}
     * {@literal a.txt     --> .}
     * </pre>
     * 
     * @param path
     * @return 
     * @throws IllegalArgumentException
     */
    public static String getFileDirPath(final String path) {
        if (path == null) {
            throw new IllegalArgumentException("path = null");
        }
        
        String filePath = new File(path).getPath();
        
        if (!filePath.contains(File.separator)) {
            return ".";
        }
        if (filePath.indexOf(File.separator) == 0 && 
                filePath.lastIndexOf(File.separator) == 0) {
           return ".";
        }
        return filePath.substring(0, filePath.lastIndexOf(File.separator));
    }   
   
    /**
     * Nahradí v zadaném textu speciální znaky souborového systému 
     * (viz {@linkplain #DENIED_PATH_CHARS}) za '-', 
     * mezery nahradí na '_', odstraní diakritiku.
     * 
     * @param name
     * @return 
     * @throws IllegalArgumentException
     */
    public static String convertToGoodFileName(String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("text is blank");
        }
        
        String result = name.replaceAll("[" + DENIED_PATH_CHARS + "]", "-");
        result = result.replaceAll("\\s", "_");        
        result = StringUtils.removeDiacriticalMarks(result);        
        return result;
    }    
    
    // TODO ^ absolutizePathToDest -- přejmenovat, doplnit javadoc, další testy .. . 
    
    /**
     * 
     * <p>
     * např:
     * <pre>
     * {@literal basePath=a;  destFilePath=a/test.jpg  -->   /home/ja/test/a/test.jpg}
     * {@literal basePath=/home/ja/test/a;  destFilePath=images/test.jpg  -->   /home/ja/test/a/images/test.jpg}
     * {@literal basePath=/home/ja/test/a/test.jpg;  destFilePath=images/test.jpg  -->   /home/ja/test/a/images/test.jpg}
     * {@literal basePath=b  destFilePath=/home/ja/test/a/test.jpg  -->   /home/ja/test/a/test.jpg}
     * </pre>
     * 
     * @param basePath
     * @param destFilePath
     * @return
     * @throws IOException 
     * @throws IllegalArgumentException
     */
    public static String absolutizePathToDest(
            String basePath, String destFilePath) 
            throws IOException {
        if (StringUtils.isBlank(basePath)) {
            throw new IllegalArgumentException("basePath is blank");
        }        
        if (StringUtils.isBlank(destFilePath)) {
            throw new IllegalArgumentException("destFilePath is blank");
        }
        
        if (new File(destFilePath).isAbsolute()) {
            return destFilePath;
        }
        
        String baseDir = FileUtils.getFileDirPath(basePath);
        if (new File(basePath).isDirectory()) {
            baseDir = basePath;
        }
        File tmp = new File(baseDir + File.separator + destFilePath);
        return tmp.getCanonicalPath();
    }    
    

    /**
     * Relativizuje cestu k souboru vzhledem k zadanému adresáři.
     * <p>
     * např:
     * <pre>
     * {@literal /home/ja/test/;  "/home/ja/test/a/test.txt"   -->   a/test.txt}
     * </pre>
     * 
     * @param baseDirPath
     * @param fullFilePath 
     * @throws IllegalArgumentException
     */
    public static String relativizePath(
            String baseDirPath, String fullFilePath) {
        if (StringUtils.isBlank(fullFilePath)) {
            throw new IllegalArgumentException("filePath is blank");
        }   
        
        String relative = new File(baseDirPath).toURI()
                .relativize(new File(fullFilePath).toURI()).getPath();
        
        String fsr = File.separator;
        if (File.separator.equals("\\")) {
             fsr = "\\\\";
        }
        relative = relative.replaceAll("[\\/\\\\]", fsr);
        
        return relative;
    }
    
    /**
     * Získá části cesty jako pole.
     * <p>
     * např:
     * <pre>
     * {@literal /home/ja/test/text.txt  -->  [home, ja, test, text.txt] }
     * </pre>
     * 
     * @param path
     * @return  prázdné pole, pokud je {@code path} prázdná
     */
    public static String[] getPartsOfPath(String path) {
        if (StringUtils.isBlank(path)) {
            return new String[0];
        }
        
        String[] fspDirs;

        fspDirs = path.split("\\" + File.separator);
        return fspDirs;
    }
    
    
    /**
     * Změní část cesty k zadanému souboru z {@code baseDirPath}
     * na {@code newBaseDirPath}.
     * Předtím než se začne cesta používat je nutné ještě vytvořit
     * adresářovou strukturu.
     * <p>
     * např:
     * <pre>
     * {@literal filePath = "/home/ja/test/text.txt"
     * baseDirPath = "/home/ja"
     * newBaseDirPath = "/opt"
     *   -->   "/opt/test/text.txt" }
     * 
     * {@literal filePath = "E:\test\test.html"
     * baseDirPath = ""
     * newBaseDirPath = "C:\supertest"
     *   -->   "C:\supertest\E\test\test.html" }
     * 
     * {@literal filePath = "/home/ja/test.html"
     * baseDirPath = ""
     * newBaseDirPath = "/opt/test"
     *   -->   "/opt/test/home/ja/test.html" }
     * </pre>
     * 
     * @param filePath
     * @param baseDirPath
     * @param newBaseDirPath
     * @return 
     * @throws IllegalArgumentException
     */
    public static String getCopyFilePathInNewDir
            (String filePath, String baseDirPath, String newBaseDirPath) {
        if (StringUtils.isBlank(filePath)) {
            throw new IllegalArgumentException("filePath is blank");
        }
        
        String newFilePath = "";
        
        if (baseDirPath.isEmpty()) {
            String filePathTemp = filePath;
            if (filePathTemp.contains(":")) {       // Windows  C:\
                filePathTemp = filePathTemp.replaceAll(":", "");
            }
            if (filePathTemp.startsWith(File.separator)) {
                filePathTemp = filePathTemp.substring(File.separator.length());
            }
            
            String newBaseDirPathCopy = 
                    appendSeparatorToDirPath(newBaseDirPath);
            
            newFilePath = newBaseDirPathCopy + filePathTemp;
        }
        else {
            String baseDirPathCopy = 
                    appendSeparatorToDirPath(baseDirPath);
            String newBaseDirPathCopy = 
                    appendSeparatorToDirPath(newBaseDirPath);

            newFilePath = filePath.replace(baseDirPathCopy, newBaseDirPathCopy);
        }
        
        return newFilePath;
    }  
    
    /**
     * Vytvoří neexistující adresáře v zadané cestě.
     * 
     * @param path 
     */
    public static void createDirs(String path) {
        new File(FileUtils.getFileDirPath(path)).mkdirs();
    }

    /**
     * Na konec adresářové cesty přidá oddělovač adresářů, pokud tam není.
     * 
     * @param dirPath
     * @return 
     */
    private static String appendSeparatorToDirPath(String dirPath) {
        if (dirPath.endsWith(File.separator)) {
            return dirPath;
        }
        else {
            return dirPath + File.separator;
        }
    }
    
    /**
     * Získá část cesty k adresáři, kterou mají společnou všechny 
     * zadané soubory.
     * <p>
     * např:
     * <pre>
     * {@literal /home/ja/test/text.txt; /home/ja/test/images/a.jpg   -->  /home/ja/test }
     * {@literal /home/ja/test_text.txt   -->  /home/ja }
     * {@literal /home/text.txt;  /home/ja/test/text.txt; /homa/ja/images/a.jpg   -->  /home }
     * </pre>
     * 
     * @return  {@code null}, pokud je zadaná kolekce prázdná; jinak 
     *      společnou část cesty
     */
    public static String getCommonDir(Collection<String> files) {
        if (files == null) {
            return null;
        }
        if (files.isEmpty()) {
            return null;
        }

        String firstFilePath = files.iterator().next();
        if (files.size() == 1) {
            String firstFileDir = getFileDirPath(firstFilePath);
            if (firstFileDir.equals(".")) {
                return "";
            }
            return firstFileDir;
        }

        String[] fspDirs = getPartsOfPath(firstFilePath);
        
        if (fspDirs.length == 0) {
            return "";
        }        
        
        // vezme první soubor ze seznamu, rozdělí na adresáře
        // /opt/a/test/test.txt --> [opt, a, test, test.txt]
        // z toho sestavuje delší a delší cestu a testuje zda všechny soubory
        //  začínají takovou (delší a delší) cestou ...
        String base = "";
        for (String dir : fspDirs) {
            
            boolean commonDir = true;
            for (String filePath : files) {

                if (! filePath.startsWith(base + dir)) {
                    commonDir = false;
                }
            }
            if (commonDir == true) {
                base += dir + File.separator;
            }
            else {
                return base;
            }
        }

        return "";
    }    
    
    
    /**
     * Načte zadaný soubor do pole bytů.
     *
     * @param file
     * @return
     * @throws IOException  chyba ve čtení souboru
     * @throws IllegalArgumentException
     */
    public static byte[] readFileToByteArray(final File file)
            throws IOException {
        return readFileToByteArray(file, file.length());
    }
  
    
    /**
     * Načte zadaný počet bytů ze souboru.
     * Čte jen od začátku
     *
     * @param file
     * @param firstBytes  čte jen zadaný počet bytů op začátku souboru
     * @return
     * @throws IOException  chyba ve čtení souboru
     * @throws IllegalArgumentException  pokud je soubor příliš velký
     */
    public static byte[] readFileToByteArray(final File file, long firstBytes)
            throws IOException {
        if (file == null) {
            throw new IllegalArgumentException("file");
        }
        if (firstBytes < 0) {
            throw new IllegalArgumentException("firstBytes < 0");
        }
                
        FileInputStream fin = new FileInputStream(file);
        long bytesForRead = firstBytes;
        if (firstBytes > file.length()) {
            bytesForRead = file.length();
        }
        if (bytesForRead > Integer.MAX_VALUE) {
            fin.close();
            throw new IllegalArgumentException("file.size > Integer.MAX_VALUE");
        }
        if (bytesForRead == 0L) {
            fin.close();
            return new byte[0];
        }
    	byte fileContent[] = new byte[(int)bytesForRead];
    	fin.read(fileContent);
    	fin.close();

        return fileContent;
    }
    
    
    /**
     * Testuje zda je zadaný soubor typu archiv zip.
     * 
     * @param filePathName
     * @return 
     * @throws IllegalArgumentException
     */
    public static boolean isZipFile(String filePathName) {
        if (StringUtils.isBlank(filePathName)) {
            throw new IllegalArgumentException("filePathName is blank");
        }        
        
        boolean result = false;
        ZipInputStream zis = null;
        try {
            zis = new ZipInputStream(
                    new FileInputStream(filePathName));
            ZipEntry entry = zis.getNextEntry();
            if (entry != null) {
                result = true;
            }
        } catch (ZipException ex) {
            // nic
        } catch (IOException ex) {
            // nic
        } finally {
            try {
                if (zis != null)  {
                    zis.close();
                }
            } catch (IOException ex) {
                // nic
            }
            return result;
        }        
    }
        
    
    /**
     * Smaže zadaný adresář, včetně všech podadresářů, nehledě na obsah.
     * 
     * @param dir  adresář ke smazání
     * @throws IOException  pokud se něco nepodařilo smazat.
     * @throws IllegalArgumentException
     */
    public static void deleteDirRecursively(final File dir)
            throws IOException {
        if (dir == null) {
            throw new IllegalArgumentException("dir = null");
        }
      
        boolean allDeleted = true;
        if (dir.isDirectory()) {
            File[] list = dir.listFiles();
            for (int i=0; i < list.length; i++) {
                if (list[i].isDirectory()) {
                    FileUtils.deleteDirRecursively(list[i]);
                }
                if (! list[i].delete()) {
//                    System.out.println("! not deleted: " + list[i]);                    
                    allDeleted = false;
                }
            }            
        }
        if (! dir.delete()) {
            allDeleted = false;
        }
        if (! allDeleted) {
            throw new IOException(
                    "\"" + dir + "\" - some files can't be deleted.");
        }
    }
    
    /**
     * Smaže zadaný adresář, včetně všech podadresářů, nehledě na obsah.
     * 
     * @param dir  adresář ke smazání
     * @throws IOException  pokud se něco nepodařilo smazat.
     * @throws IllegalArgumentException
     */
    public static void deleteDirRecursively(final String dir)
            throws IOException {
        if (StringUtils.isBlank(dir)) {
            throw new IllegalArgumentException("dir = null");
        }
        
        FileUtils.deleteDirRecursively(new File(dir));
    } 
    
    
    /**
     * 
     * @param path
     * @return 
     * @throws IllegalArgumentException
     */
    public static String getAbsoluteFilePath(String path) {
        if (StringUtils.isBlank(path)) {
            throw new IllegalArgumentException("path is blank");
        }
        
        File file = new File(path);
        return getAbsoluteFilePath(file);
    }

    /**
     * 
     * @param filePath
     * @return 
     * @throws IllegalArgumentException
     */
    public static String getAbsoluteFilePath(File filePath) {
        if (filePath == null) {
            throw new IllegalArgumentException("filePath = null");
        }
        
        try {
            return filePath.getCanonicalPath();
        } catch (IOException ex) {
            return filePath.getAbsolutePath();
        }        
    }
    
    /**
     * 
     * @param dirName
     * @return 
     */
    public static boolean isDirectory(String dirName) {
        if (StringUtils.isBlank(dirName)) {
            return false;
        }
        File f = new File(dirName);
        if (f.isFile() && f.isDirectory()) {
            return true;
        }
        return false;
    }

    /**
     * Zjistí zda jméno souboru neobsahuje nepovolené znaky. Nepovolené znaky
     * jsou v {@linkplain #DENIED_PATH_CHARS}
     * 
     * @param fileName
     *      jméno souboru, který se testuje na nepovolené znaky
     * @return {@code true}, pokud je jméno souboru v pořádku, 
     *      tj. neobsahuje nepovolené znaky
     * @throws IllegalArgumentException
     */    
    public static boolean isValidFileName(final String fileName) {
        if (fileName == null) {
            throw new IllegalArgumentException("fileName = null");
        }
                
        Pattern p = Pattern.compile("[" + DENIED_PATH_CHARS + "]");
        Matcher m = p.matcher(fileName);     
        return !(m.find());
    } 
    
    /**
     * Získá seznam souborů v zadaném adresáři z všech jeho podadresářích. 
     * Soubory nevyhovující filtru ignoruje.
     * 
     * @param rootDir  cesta adresáře, ve kterém se hledá
     * @param filter  filtr souborů, nebo {@code null}, pro všechny soubory
     * @return seznam souborů v zadaném adresáři z všech jeho podadresářích
     * @throws java.lang.IllegalArgumentException
     */
    public static ArrayList<File> getListOfFiles (final File rootDir, 
            final FileFilter filter) 
            throws IllegalArgumentException {
        if (rootDir == null) {
            throw new IllegalArgumentException("rootDir is null");
        }
        if (filter == null) {
            throw new IllegalArgumentException("filter is null");
        }
        
        ArrayList<File> fileList = new ArrayList<File>(100);
        
        if (rootDir.isDirectory()) {
            // pomocný seznam fungující jako zásobník; nahrazuje rekurzi
            LinkedList<File> tmpList = new LinkedList<File>();
            if (filter == null) {
                tmpList.addAll(Arrays.asList(rootDir.listFiles()));
            }
            else {
                tmpList.addAll(Arrays.asList(rootDir.listFiles(filter)));
            }
            
            while (tmpList.size() > 0) {
                File file = tmpList.removeFirst();
                if (file.isFile()) {
                    fileList.add(file);
                }
                if (file.isDirectory()) {
                    if (filter == null) {
                        tmpList.addAll(Arrays.asList(file.listFiles()));
                    }
                    else {
                        tmpList.addAll(Arrays.asList(file.listFiles(filter)));
                    }
                }
            }
            return fileList;
        }
        
        throw new IllegalArgumentException(rootDir + " not found");
    }
    
    /**
     * Získá seznam všech souborů v zadaném adresáři z všech jeho podadresářích. 
     * 
     * @param rootDir  cesta adresáře, ve kterém se hledá
     * @return seznam souborů v zadaném adresáři z všech jeho podadresářích
     * @throws java.lang.IllegalArgumentException
     */
    public static ArrayList getListOfFiles (final String rootDir)  
            throws IllegalArgumentException {
        return getListOfFiles(new File(rootDir), null);
    }
    
    /**
     * Získá velikost soboru formátovanou pro lepší čitelnost.
     * <p>
     * např:
     * <tt><pre>
     * 899 B
     * 1505 B
     * 55.8 KiB
     * 14.0 MiB
     * </pre></tt>
     * 
     * @param file
     * @return 
     */
    public static String formatFileSize(File file) {
        return formatFileSize(file.length());
    }
    
    /**
     * Získá velikost soboru formátovanou pro lepší čitelnost.
     * <p>
     * např:
     * <tt><pre>
     * 899 B
     * 1505 B
     * 55.8 KiB
     * 14.0 MiB
     * </pre></tt>
     * 
     * @param fileSize
     * @return 
     */
    public static String formatFileSize(long fileSize) {
        if (fileSize > (10*1024*1024)) {
            return NumberUtils.convertToString(
                    ((double)fileSize)/(1024*1024),1 ) 
                    + " MiB";
        }
        else if (fileSize > (10*1024)) {
             return NumberUtils.convertToString(
                    ((double)fileSize)/1024,1 ) 
                     + " KiB";
        }
        else {
             return fileSize + " B";
        }        
    }    
    
    
    /* --------------------------------------------------------------------*/
    
    /**
     * Vytvoří jednoduchý filtr, který propustí pouze 
     * soubory se zadanými příponami a adresáře.
     * 
     * @param fileExt  povolené přípony, bez tečky (např: <tt>txt</tt>)
     * @return filtr propouštějící soubory se zadanou příponou
     * @see File#listFiles(java.io.FileFilter)
     */
    public static java.io.FileFilter createSimpleExtFileFilter (
            final String... extensions) {
        java.io.FileFilter filter = new java.io.FileFilter() {
            @Override
            public boolean accept(File file) {
                boolean hasExt = FileUtils.hasFileExtension(
                        file.getName(), extensions);
                boolean isDir = file.isDirectory();
                return isDir || hasExt;
            }
        };
        return filter;
    }
    
    /**
     * Vytvoří jednoduchý filtr, který propustí pouze 
     * soubory se zadanými příponami a adresáře(musí být).
     * Pro dialog pro otevření nebo uložení souboru ve Swingu.
     * 
     * @param fileExt  povolené přípony, bez tečky (např: <tt>txt</tt>)
     * @param description  popis filtru v dialogu 
     *      (např: <tt>Textové soubory (*.txt)</tt>)
     * @return filtr propouštějící soubory se zadanou příponou
     */
    public static javax.swing.filechooser.FileFilter createSimpleExtFileFilter (
            final String description, final String... extensions) {
        javax.swing.filechooser.FileFilter filter = 
                new javax.swing.filechooser.FileFilter() {
            @Override
            public boolean accept(File file) {
                // podmínka musí být -- ve Windows, když se klikne na [Tento počítač] v postranním panelu ...
                if (StringUtils.isBlank(file.getName())) {   
                    return true;
                }
                boolean hasExt = FileUtils.hasFileExtension(
                        file.getName(), extensions);
                boolean isDir = file.isDirectory();
                return isDir || hasExt;
            }
            @Override
            public String getDescription() {
                return description;
            }            
        };
        return filter;
    }    
    
    /**
     * Vytvoří jednoduchý filtr, který propustí pouze 
     * soubory se zadanými příponami.
     * 
     * @param dirsEnabled
     * @param fileExt  povolené přípony, bez tečky (např: <tt>txt</tt>)
     * @return filtr propouštějící soubory se zadanou příponou
     * @see File#list(java.io.FilenameFilter) 
     */
    public static java.io.FilenameFilter createSimpleExtFilenameFilter (
            final boolean dirsEnabled,
            final String... extensions) {
        java.io.FilenameFilter filter = new java.io.FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if (new File(name).isDirectory()) {
                    return dirsEnabled;
                }
                boolean hasExt = FileUtils.hasFileExtension(name, extensions);
                return hasExt;
            }
        };
        return filter;
    }    
    
}   // FileUtils.java

